home *** CD-ROM | disk | FTP | other *** search
- # Source Generated with Decompyle++
- # File: in.pyc (Python 2.6)
-
- '''
- Resource protection for Nevow. If you wish to use twisted.cred to protect your
- Nevow application, you are probably most interested in
- L{SessionWrapper}.
- '''
- __metaclass__ = type
- import random
- import time
- import md5
- import StringIO
- from zope.interface import implements
- from twisted.python import log, components
- from twisted.internet import defer
- from twisted.cred.error import UnauthorizedLogin
- from twisted.cred.credentials import UsernamePassword, Anonymous
-
- try:
- from twisted.web import http
- except ImportError:
- from twisted.protocols import http
-
- from nevow import inevow, url, stan
-
- def _sessionCookie():
- return md5.new('%s_%s' % (str(random.random()), str(time.time()))).hexdigest()
-
-
- class GuardSession(components.Componentized):
- """A user's session with a system.
-
- This utility class contains no functionality, but is used to
- represent a session.
- """
- implements(inevow.ISession, inevow.IGuardSession)
-
- def __init__(self, guard, uid):
- '''Initialize a session with a unique ID for that session.
- '''
- components.Componentized.__init__(self)
- self.guard = guard
- self.uid = uid
- self.expireCallbacks = []
- self.checkExpiredID = None
- self.setLifetime(60)
- self.portals = { }
- self.touch()
-
-
- def getLoggedInRoot(self):
- '''Get the most-recently-logged-in avatar.
- '''
- if len(self.portals) != 1:
- raise RuntimeError('Ambiguous request for current avatar.')
- len(self.portals) != 1
- return self.portals.values()[0][0]
-
-
- def resourceForPortal(self, port):
- return self.portals.get(port)
-
-
- def setDefaultResource(self, rsrc, logout):
- '''
-
- Change the root-resource available to the user who has already
- authenticated anonymously. This only works in applications that DO NOT
- use the multiple-simultaneous-portals feature. If you do not know what
- this means, you may safely ignore it.
-
- '''
- if len(self.portals) != 1:
- raise RuntimeError('Ambiguous request for current avatar.')
- len(self.portals) != 1
- self.setResourceForPortal(rsrc, self.portals.keys()[0], logout)
-
-
- def setResourceForPortal(self, rsrc, port, logout):
- '''Change the root-resource available to a user authenticating against a given
- portal.
-
- If a user was already logged in to this session from that portal, first
- log them out.
-
- @param rsrc: an L{IResource} implementor.
- @param port: a cred Portal instance.
- @param logout: a 0-arg callable to be invoked upon logout.
- '''
- self.portalLogout(port)
- self.portals[port] = (rsrc, logout)
- return rsrc
-
-
- def portalLogout(self, port):
- '''
- If we have previously acccepted a login for this portal, call its
- logout method and de-associate that portal from this session, catching
- any errors from the logout method.
-
- Otherwise: do nothing.
-
- @param port: a cred Portal.
- '''
- p = self.portals.get(port)
- if p:
- log.msg('Logout of portal %r' % port)
- (r, l) = p
-
- try:
- l()
- except:
- log.err()
-
- del self.portals[port]
-
-
-
- def setLifetime(self, lifetime):
- """Set the approximate lifetime of this session, in seconds.
-
- This is highly imprecise, but it allows you to set some general
- parameters about when this session will expire. A callback will be
- scheduled each 'lifetime' seconds, and if I have not been 'touch()'ed
- in half a lifetime, I will be immediately expired.
- """
- self.lifetime = lifetime
-
-
- def notifyOnExpire(self, callback):
- '''Call this callback when the session expires or logs out.
- '''
- self.expireCallbacks.append(callback)
-
-
- def expire(self):
- '''Expire/logout of the session.
- '''
- log.msg('expired session %s' % str(self.uid))
- del self.guard.sessions[self.uid]
- for portal in self.portals.keys():
- self.portalLogout(portal)
-
- for c in self.expireCallbacks:
-
- try:
- c()
- continue
- log.err()
- continue
-
-
- self.expireCallbacks = []
- if self.checkExpiredID:
- self.checkExpiredID.cancel()
- self.checkExpiredID = None
-
-
-
- def touch(self):
- self.lastModified = time.time()
-
-
- def checkExpired(self):
- reactor = reactor
- import twisted.internet
- self.checkExpiredID = None
- if time.time() - self.lastModified > self.lifetime / 2:
- if self.guard.sessions.has_key(self.uid):
- self.expire()
- else:
- log.msg('no session to expire: %s' % str(self.uid))
- else:
- log.msg('session given the will to live for %s more seconds' % self.lifetime)
- self.checkExpiredID = reactor.callLater(self.lifetime, self.checkExpired)
-
-
- def __getstate__(self):
- d = self.__dict__.copy()
- if d.has_key('checkExpiredID'):
- del d['checkExpiredID']
-
- return d
-
-
- def __setstate__(self, d):
- self.__dict__.update(d)
- self.touch()
- self.checkExpired()
-
-
-
- def urlToChild(ctx, *ar, **kw):
- u = url.URL.fromContext(ctx)
- for segment in ar:
- u = u.child(stan.xml(segment))
-
- if inevow.IRequest(ctx).method == 'POST':
- u = u.clear()
-
- for k, v in kw.items():
- u = u.replace(k, v)
-
- return u
-
- SESSION_KEY = '__session_key__'
- LOGIN_AVATAR = '__login__'
- LOGOUT_AVATAR = '__logout__'
-
- def nomind(*args):
- pass
-
-
- class Forbidden(object):
- implements(inevow.IResource)
-
- def locateChild(self, ctx, segments):
- return self
-
-
- def renderHTTP(self, ctx):
- request = inevow.IRequest(ctx)
- request.setResponseCode(http.FORBIDDEN)
- return '<html><head><title>Forbidden</title></head><body><h1>Forbidden</h1>Request was forbidden.</body></html>'
-
-
-
- class SessionWrapper:
- '''
- SessionWrapper
-
- The following class attributes can be modified on an instance
- of the class.
-
- @ivar secureCookies: Whether to use secure (TLS only) cookies or not.
- True (default): make cookies secure when session is initiated
- in a secure (TLS) connection.
-
- False: cookies do not get the secure attribute.
-
- @ivar: persistentCookies: Whether to use persistent (saved to disk) cookies or not.
- True: make cookies persistent, so they are valid for the
- length of the sessionLifetime even if the browser window
- is closed.
-
- False (default): cookies do not get saved to disk, and thus last
- only as long as the session does. If the browser is
- closed before the session timeout, both the session
- and the cookie go away.
- '''
- implements(inevow.IResource)
- sessionLifetime = 3600
- sessionFactory = GuardSession
- credInterface = inevow.IResource
- useCookies = True
- secureCookies = True
- persistentCookies = False
-
- def __init__(self, portal, cookieKey = None, mindFactory = None, credInterface = None, useCookies = None):
- self.portal = portal
- if cookieKey is None:
- cookieKey = 'woven_session_' + _sessionCookie()
-
- self.cookieKey = cookieKey
- self.sessions = { }
- if mindFactory is None:
- mindFactory = nomind
-
- self.mindFactory = mindFactory
- if credInterface is not None:
- self.credInterface = credInterface
-
- if useCookies is not None:
- self.useCookies = useCookies
-
- self.resource = self
-
-
- def renderHTTP(self, ctx):
- request = inevow.IRequest(ctx)
- d = defer.maybeDeferred(self._delegate, ctx, [])
-
- def _cb(.0, ctx):
- (resource, segments) = .0
- if not not segments:
- raise AssertionError
- res = inevow.IResource(resource)
- return res.renderHTTP(ctx)
-
- d.addCallback(_cb, ctx)
- return d
-
-
- def locateChild(self, ctx, segments):
- request = inevow.IRequest(ctx)
- path = segments[0]
- if self.useCookies:
- cookie = request.getCookie(self.cookieKey)
- else:
- cookie = ''
- if path.startswith(SESSION_KEY):
- key = path[len(SESSION_KEY):]
- if key not in self.sessions:
- return (urlToChild(ctx, *segments[1:], **{
- '__start_session__': 1 }), ())
- self.sessions[key].setLifetime(self.sessionLifetime)
- if cookie == key:
- self.sessions[key].sessionJustStarted = True
- return (urlToChild(ctx, *segments[1:]), ())
- return self.checkLogin(ctx, self.sessions[key], segments[1:], sessionURL = segments[0])
- path.startswith(SESSION_KEY)
- return self._delegate(ctx, segments)
-
-
- def _delegate(self, ctx, segments):
- """Identify the session by looking at cookies and HTTP auth headers, use that
- session key to identify the wrapped resource, then return a deferred
- which fires a 2-tuple of (resource, segments) to the top-level
- redirection code code which will delegate IResource's renderHTTP or
- locateChild methods to it
- """
- request = inevow.IRequest(ctx)
- cookie = request.getCookie(self.cookieKey)
- userpass = (request.getUser(), request.getPassword())
- httpAuthSessionKey = 'HTTP AUTH: %s:%s' % userpass
- for sessionKey in (cookie, httpAuthSessionKey):
- if sessionKey in self.sessions:
- session = self.sessions[sessionKey]
- return self.checkLogin(ctx, session, segments)
-
- if userpass != ('', ''):
- sz.checkExpired()
- return self.checkLogin(ctx, sz, segments, None, UsernamePassword(*userpass))
- rd = self.createSession(ctx, segments)
- return (rd, ())
-
-
- def createSession(self, ctx, segments):
- '''
- Create a new session for this request, and redirect back to the path
- given by segments.
- '''
- request = inevow.IRequest(ctx)
- newCookie = _sessionCookie()
- if self.useCookies:
- if self.secureCookies and request.isSecure():
- secure = True
- else:
- secure = False
- if self.persistentCookies and self.sessionLifetime:
- expires = http.datetimeToString(time.time() + self.sessionLifetime)
- else:
- expires = None
- request.addCookie(self.cookieKey, newCookie, path = '/%s' % '/'.join(request.prepath), secure = secure, expires = expires, domain = self.cookieDomainForRequest(request))
-
- sz = self.sessions[newCookie] = self.sessionFactory(self, newCookie)
- sz.args = request.args
- sz.fields = request.fields
- sz.method = request.method
- sz.received_headers = request.received_headers
- sz.checkExpired()
- return urlToChild(ctx, SESSION_KEY + newCookie, *segments)
-
-
- def checkLogin(self, ctx, session, segments, sessionURL = None, httpAuthCredentials = None):
- '''
- Associate the given request with the given session and:
-
- - log the user in to our portal, if they are accessing a login URL
-
- - log the user out from our portal (calling their logout callback),
- if they are logged in and accessing a logout URL
-
- - Move the request parameters saved on the session, if there are
- any, onto the request if a session just started or a login
- just succeeded.
-
- @return:
-
- - if the user is already logged in: a 2-tuple of requestObject,
- C{segments} (i.e. the segments parameter)
-
- - if the user is not logged in and not logging in, call login() to
- initialize an anonymous session, and return a 2-tuple of
- (rootResource, segments-parameter) from that anonymous session.
- This counts as logging in for the purpose of future calls to
- checkLogin.
-
- - if the user is accessing a login URL: a 2-tuple of the logged in
- resource object root and the remainder of the segments (i.e. the
- URL minus __login__) to be passed to that resource.
-
- '''
- request = inevow.IRequest(ctx)
- session.touch()
- request.session = session
- root = url.URL.fromContext(request)
- if sessionURL is not None:
- root = root.child(sessionURL)
-
- request.rememberRootURL(str(root))
- spoof = False
- if getattr(session, 'sessionJustStarted', False):
- del session.sessionJustStarted
- spoof = True
-
- if getattr(session, 'justLoggedIn', False):
- del session.justLoggedIn
- spoof = True
-
- if spoof and hasattr(session, 'args'):
- request.args = session.args
- request.fields = session.fields
- request.content = StringIO.StringIO()
- request.content.close()
- request.method = session.method
- request.received_headers = session.received_headers
- del session.args
- del session.fields
- del session.method
- del session.received_headers
-
- if segments and segments[0] in (LOGIN_AVATAR, LOGOUT_AVATAR):
- authCommand = segments[0]
- else:
- authCommand = None
- if httpAuthCredentials:
- if not not authCommand:
- raise AssertionError, "HTTP auth support isn't that robust. Come up with something to do that makes sense here."
- return self.login(request, session, httpAuthCredentials, segments).addErrback(self.authRequiredError, session)
- if authCommand == LOGIN_AVATAR:
- subSegments = segments[1:]
-
- def unmangleURL(.0):
- (res, segs) = .0
- session.justLoggedIn = True
- u = url.URL.fromString(request.getRootURL())
- for seg in subSegments:
- u = u.child(seg)
-
- return (u, ())
-
- return self.login(request, session, self.getCredentials(request), subSegments).addCallback(unmangleURL).addErrback(self.incorrectLoginError, ctx, subSegments, 'Incorrect login.')
- if authCommand == LOGOUT_AVATAR:
- self.explicitLogout(session)
- return (urlToChild(ctx, *segments[1:]), ())
- r = session.resourceForPortal(self.portal)
- if r:
- return (r[0], segments)
- return self.login(request, session, Anonymous(), segments).addErrback(self.incorrectLoginError, ctx, segments, 'Anonymous access not allowed.')
-
-
- def explicitLogout(self, session):
- """Hook to be overridden if you care about user-requested logout.
-
- Note: there is no return value from this method; it is purely a way to
- provide customized behavior that distinguishes between session-expiry
- logout, which is what 99% of code cares about, and explicit user
- logout, which you may need to be notified of if (for example) your
- application sets other HTTP cookies which refer to server-side state,
- and you want to expire that state in a manual logout but not with an
- automated logout. (c.f. Quotient's persistent sessions.)
-
- If you want the user to see a customized logout page, just generate a
- logout link that looks like
-
- http://your-site.example.com/__logout__/my/custom/logout/stuff
-
- and the user will see
-
- http://your-site.example.com/my/custom/logout/stuff
-
- as their first URL after becoming anonymous again.
- """
- session.portalLogout(self.portal)
-
-
- def getCredentials(self, request):
- username = request.args.get('username', [
- ''])[0]
- password = request.args.get('password', [
- ''])[0]
- return UsernamePassword(username, password)
-
-
- def login(self, request, session, credentials, segments):
- """
-
- - Calls login() on our portal.
-
- - creates a mind from my mindFactory, with the request and credentials
-
- - Associates the mind with the given session.
-
- - Associates the resource returned from my portal's login() with my
- portal in the given session.
-
- @return: a Deferred which fires a 2-tuple of the resource returned from
- my portal's login() and the passed list of segments upon successful
- login.
-
- """
- mind = self.mindFactory(request, credentials)
- session.mind = mind
- return self.portal.login(credentials, mind, self.credInterface).addCallback(self._cbLoginSuccess, session, segments)
-
-
- def _cbLoginSuccess(self, .1, session, segments):
- (iface, res, logout) = .1
- session.setResourceForPortal(res, self.portal, logout)
- return (res, segments)
-
-
- def incorrectLoginError(self, error, ctx, segments, loginFailure):
- """ Used as an errback upon failed login, returns a 2-tuple of a failure URL
- with the query argument 'login-failure' set to the parameter
- loginFailure, and an empty list of segments, to redirect to that URL.
- The basis for this error URL, i.e. the part before the query string, is
- taken either from the 'referer' header from the given request if one
- exists, or a computed URL that points at the same page that the user is
- currently looking at to attempt login. Any existing query string will
- be stripped.
- """
- request = inevow.IRequest(ctx)
- error.trap(UnauthorizedLogin)
- referer = request.getHeader('referer')
- if referer is not None:
- u = url.URL.fromString(referer)
- else:
- u = urlToChild(ctx, *segments)
- u = u.clear()
- u = u.add('login-failure', loginFailure)
- return (u, ())
-
-
- def authRequiredError(self, error, session):
- session.expire()
- error.trap(UnauthorizedLogin)
- return (Forbidden(), ())
-
-
- def cookieDomainForRequest(self, request):
- '''
- Specify the domain restriction on the session cookie.
-
- @param request: The request object in response to which a cookie is
- being set.
-
- @return: C{None} or a C{str} giving the domain restriction to set on
- the cookie.
- '''
- pass
-
-
-